/*
 * Copyright (c) 2025 Huawei Device Co., Ltd.
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#include "aot_args_handler.h"

#include <charconv>
#include <cstdio>
#include <fstream>
#include <nlohmann/json.hpp>

#include "aot_args_list.h"
#include "aot_compiler_constants.h"
#include "ecmascript/log_wrapper.h"
#include "ecmascript/platform/file.h"

#ifdef ENABLE_COMPILER_SERVICE_GET_PARAMETER
#include "parameters.h"
#endif

namespace OHOS::ArkCompiler {
const std::string AOT_FILE = "aot-file";
const std::string COMPILER_MODE = "target-compiler-mode";
const std::string PARTIAL = "partial";
const std::string COMPILER_PKG_INFO = "compiler-pkg-info";
const std::string PATH = "path";

const std::string STATIC_BOOT_PANDA_FILES = "boot-panda-files";
const std::string STATIC_PAOC_PANDA_FILES = "paoc-panda-files";
const std::string STATIC_PAOC_LOCATION = "paoc-location";
const std::string STATIC_PAOC_OUTPUT = "paoc-output";
const std::string STATIC_PAOC_USE_PROFILE = "paoc-use-profile";
const std::string STATIC_BOOT_PATH = "/system/framework/bootpath.json";

const std::string ARKTS_DYNAMIC = "dynamic";
const std::string ARKTS_STATIC = "static";
const std::string AN_FILE_NAME = "anFileName";
const std::string ARKTS_HYBRID = "hybrid";

const std::string AN_SUFFIX = ".an";
const std::string APP_SANBOX_PATH_PREFIX = "/data/storage/el1/bundle/";
const std::string ETS_PATH = "/ets";
const std::string OWNERID_SHARED_TAG = "SHARED_LIB_ID";

#ifdef ENABLE_COMPILER_SERVICE_GET_PARAMETER
// disable on master branch defaultly, only enable on feature branch
const bool ARK_AOT_ENABLE_STATIC_COMPILER_DEFAULT_VALUE = false;
#endif

AOTArgsHandler::AOTArgsHandler(const std::unordered_map<std::string, std::string> &argsMap) : argsMap_(argsMap)
{
#ifdef ENABLE_COMPILER_SERVICE_GET_PARAMETER
    SetIsEnableStaticCompiler(AOTArgsParserBase::IsEnableStaticCompiler());
#endif
    SetParser(argsMap);
}

void AOTArgsHandler::SetParser(const std::unordered_map<std::string, std::string> &argsMap)
{
    auto parserOpt = AOTArgsParserFactory::GetParser(argsMap, IsEnableStaticCompiler());
    if (parserOpt.has_value()) {
        parser_ = std::move(parserOpt.value());
    } else {
        parser_ = nullptr;
    }
}

int32_t AOTArgsHandler::Handle(int32_t thermalLevel)
{
    if (argsMap_.empty()) {
        LOG_SA(ERROR) << "pass empty args to aot sa";
        return ERR_AOT_COMPILER_PARAM_FAILED;
    }
    if (!parser_) {
        LOG_SA(ERROR) << "AOTArgsParser is null, invalid parameters";
        return ERR_AOT_COMPILER_PARAM_FAILED;
    }

    std::lock_guard<std::mutex> lock(hapArgsMutex_);
    int32_t ret = parser_->Parse(argsMap_, hapArgs_, thermalLevel);
    return ret;
}

std::vector<const char*> AOTArgsHandler::GetAotArgs() const
{
    std::lock_guard<std::mutex> lock(hapArgsMutex_);
    std::vector<const char*> argv;
    argv.reserve(hapArgs_.argVector.size() + 1);  // 1: for nullptr
    for (auto &arg : hapArgs_.argVector) {
        argv.emplace_back(arg.c_str());
    }

    return argv;
}

void AOTArgsHandler::GetBundleId(int32_t &bundleUid, int32_t &bundleGid) const
{
    std::lock_guard<std::mutex> lock(hapArgsMutex_);
    bundleUid = hapArgs_.bundleUid;
    bundleGid = hapArgs_.bundleGid;
}

std::string AOTArgsHandler::GetFileName() const
{
    std::lock_guard<std::mutex> lock(hapArgsMutex_);
    return hapArgs_.fileName;
}

std::string AOTArgsHandler::GetCodeSignArgs() const
{
    std::lock_guard<std::mutex> lock(hapArgsMutex_);
    return hapArgs_.signature;
}

int32_t AOTArgsParserBase::FindArgsIdxToInteger(const std::unordered_map<std::string, std::string> &argsMap,
                                                const std::string &keyName, int32_t &bundleID)
{
    if (argsMap.find(keyName) == argsMap.end()) {
        return ERR_AOT_COMPILER_PARAM_FAILED;
    }

    if (argsMap.at(keyName).empty() || !isdigit(argsMap.at(keyName).at(0))) {
        return ERR_AOT_COMPILER_PARAM_FAILED;
    }

    const char* beginPtr = argsMap.at(keyName).data();
    const char* endPtr = argsMap.at(keyName).data() + argsMap.at(keyName).size();
    auto res = std::from_chars(beginPtr, endPtr, bundleID);
    if ((res.ec != std::errc()) || (res.ptr != endPtr)) {
        LOG_SA(ERROR) << "trigger exception as converting string to integer";
        return ERR_AOT_COMPILER_PARAM_FAILED;
    }
    return ERR_OK;
}

int32_t AOTArgsParserBase::FindArgsIdxToString(const std::unordered_map<std::string, std::string> &argsMap,
                                               const std::string &keyName, std::string &bundleArg)
{
    if (argsMap.find(keyName) == argsMap.end()) {
        return ERR_AOT_COMPILER_PARAM_FAILED;
    }

    bundleArg = argsMap.at(keyName);
    return ERR_OK;
}

int32_t AOTArgsParser::Parse(const std::unordered_map<std::string, std::string> &argsMap, HapArgs &hapArgs,
                             int32_t thermalLevel)
{
    std::string abcPath;
    if ((FindArgsIdxToInteger(argsMap, ArgsIdx::BUNDLE_UID, hapArgs.bundleUid) != ERR_OK)   ||
        (FindArgsIdxToInteger(argsMap, ArgsIdx::BUNDLE_GID, hapArgs.bundleGid) != ERR_OK)   ||
        (FindArgsIdxToString(argsMap, ArgsIdx::AN_FILE_NAME, hapArgs.fileName) != ERR_OK)   ||
        (FindArgsIdxToString(argsMap, ArgsIdx::APP_SIGNATURE, hapArgs.signature) != ERR_OK) ||
        (FindArgsIdxToString(argsMap, ArgsIdx::ABC_PATH, abcPath) != ERR_OK)) {
        LOG_SA(ERROR) << "aot compiler args parsing error";
        return ERR_AOT_COMPILER_PARAM_FAILED;
    }

    hapArgs.argVector.clear();
    hapArgs.argVector.emplace_back(AOT_EXE);

    // service process add aot compile args here
    AddExpandArgs(hapArgs.argVector, thermalLevel);

    for (auto &argPair : argsMap) {
        if (aotArgsList.find(argPair.first) != aotArgsList.end()) {
            hapArgs.argVector.emplace_back(Symbols::PREFIX + argPair.first + Symbols::EQ + argPair.second);
        }
    }

#ifdef ENABLE_COMPILER_SERVICE_GET_PARAMETER
    SetEnableCodeCommentBySysParam(hapArgs);
    SetAnFileMaxSizeBySysParam(hapArgs);
#endif

    hapArgs.argVector.emplace_back(abcPath);
    return ERR_OK;
}

#ifdef ENABLE_COMPILER_SERVICE_GET_PARAMETER
void AOTArgsParser::SetAnFileMaxSizeBySysParam(HapArgs &hapArgs)
{
    int anFileMaxSize = OHOS::system::GetIntParameter<int>("ark.aot.compiler_an_file_max_size", -1);
    if (anFileMaxSize >= 0) {
        hapArgs.argVector.emplace_back(Symbols::PREFIX + ArgsIdx::COMPILER_AN_FILE_MAX_SIZE + Symbols::EQ +
                                       std::to_string(anFileMaxSize));
    }
}

void AOTArgsParser::SetEnableCodeCommentBySysParam(HapArgs &hapArgs)
{
    bool enableAotCodeComment = OHOS::system::GetBoolParameter("ark.aot.code_comment.enable", false);
    if (enableAotCodeComment) {
        hapArgs.argVector.emplace_back(Symbols::PREFIX + ArgsIdx::COMPILER_ENABLE_AOT_CODE_COMMENT + Symbols::EQ +
                                       "true");
        hapArgs.argVector.emplace_back(Symbols::PREFIX + ArgsIdx::COMPILER_LOG_OPT + Symbols::EQ + "allasm");
    }
}

bool AOTArgsParserBase::IsEnableStaticCompiler()
{
    bool enable = OHOS::system::GetBoolParameter("ark.aot.enable_static_compiler",
        ARK_AOT_ENABLE_STATIC_COMPILER_DEFAULT_VALUE);
    LOG_SA(INFO) << "enable static compiler switch is : " << enable;
    return enable;
}
#endif

void AOTArgsParser::AddExpandArgs(std::vector<std::string> &argVector, int32_t thermalLevel)
{
    std::string thermalLevelArg = "--compiler-thermal-level=" + std::to_string(thermalLevel);
    argVector.emplace_back(thermalLevelArg);
}

int32_t StaticAOTArgsParser::Parse(const std::unordered_map<std::string, std::string> &argsMap,
                                   HapArgs &hapArgs, [[maybe_unused]] int32_t thermalLevel)
{
    std::string abcPath;
    if ((FindArgsIdxToInteger(argsMap, ArgsIdx::BUNDLE_UID, hapArgs.bundleUid) != ERR_OK)   ||
        (FindArgsIdxToInteger(argsMap, ArgsIdx::BUNDLE_GID, hapArgs.bundleGid) != ERR_OK)   ||
        (FindArgsIdxToString(argsMap, ArgsIdx::AN_FILE_NAME, hapArgs.fileName) != ERR_OK)   ||
        (FindArgsIdxToString(argsMap, ArgsIdx::APP_SIGNATURE, hapArgs.signature) != ERR_OK) ||
        (FindArgsIdxToString(argsMap, ArgsIdx::ABC_PATH, abcPath) != ERR_OK)) {
        LOG_SA(ERROR) << "aot compiler args parsing error";
        return ERR_AOT_COMPILER_PARAM_FAILED;
    }

    hapArgs.argVector.clear();
    hapArgs.argVector.emplace_back(STATIC_AOT_EXE);

    for (auto &defaultArg : staticAOTDefaultArgs) {
        hapArgs.argVector.emplace_back(defaultArg);
    }

    std::string bootfiles;
    if (!ParseBootPandaFiles(bootfiles)) {
        return ERR_AOT_COMPILER_PARAM_FAILED;
    }
    hapArgs.argVector.emplace_back(Symbols::PREFIX + STATIC_BOOT_PANDA_FILES + Symbols::EQ + bootfiles);

    std::string anfilePath;
    std::string pkgInfo;
    bool partialMode = false;
    for (auto &argPair : argsMap) {
        // for 1.2, replace aot-file by paoc-output
        if (argPair.first == AOT_FILE) {
            anfilePath = argPair.second;
            std::string anFileName = anfilePath + AN_SUFFIX;
            hapArgs.argVector.emplace_back(Symbols::PREFIX + STATIC_PAOC_OUTPUT + Symbols::EQ + anFileName);
            continue;
        }

        if (argPair.first == COMPILER_MODE && argPair.second == PARTIAL) {
            partialMode = true;
            continue;
        }

        if (argPair.first == COMPILER_PKG_INFO) {
            pkgInfo = argPair.second;
            continue;
        }

        if (staticAOTArgsList.find(argPair.first) != staticAOTArgsList.end()) {
            hapArgs.argVector.emplace_back(Symbols::PREFIX + argPair.first + Symbols::EQ + argPair.second);
        }
    }

    std::string location = ParseLocation(anfilePath);
    hapArgs.argVector.emplace_back(Symbols::PREFIX + STATIC_PAOC_LOCATION + Symbols::EQ + location);
    hapArgs.argVector.emplace_back(Symbols::PREFIX + STATIC_PAOC_PANDA_FILES + Symbols::EQ + abcPath);

    if (partialMode && !ParseProfileUse(hapArgs, pkgInfo)) {
        return ERR_AOT_COMPILER_PARAM_FAILED;
    }

    return ERR_OK;
}

bool StaticAOTArgsParser::ParseBootPandaFiles(std::string &bootfiles)
{
    std::ifstream inFile;
    inFile.open(STATIC_BOOT_PATH, std::ios::in);
    if (!inFile.is_open()) {
        LOG_SA(ERROR) << "read json error";
        return false;
    }
    nlohmann::json jsonObject = nlohmann::json::parse(inFile);
    if (jsonObject.is_discarded()) {
        LOG_SA(ERROR) << "json discarded error";
        inFile.close();
        return false;
    }

    if (jsonObject.is_null() || jsonObject.empty()) {
        LOG_SA(ERROR) << "invalid json";
        inFile.close();
        return false;
    }

    for (const auto &[key, value] : jsonObject.items()) {
        if (!value.is_null() && value.is_string()) {
            std::string jsonValue = value.get<std::string>();
            if (jsonValue.empty()) {
                LOG_SA(ERROR) << "json value of " << key << " is empty";
                continue;
            }
            if (!bootfiles.empty()) {
                bootfiles += ":";
            }
            bootfiles += jsonValue.c_str();
        }
    }
    inFile.close();
    return true;
}

std::string StaticAOTArgsParser::ParseLocation(std::string &anFilePath)
{
    size_t pos = anFilePath.find_last_of("/");
    if (pos == std::string::npos) {
        LOG_SA(FATAL) << "aot sa parse invalid location";
    }
    std::string moduleName = anFilePath.substr(pos + 1);
    std::string location = APP_SANBOX_PATH_PREFIX + moduleName + ETS_PATH;
    return location;
}

bool StaticAOTArgsParser::ParseProfilePath(std::string &pkgInfo, std::string &profilePath)
{
    nlohmann::json jsonPkgInfo = nlohmann::json::parse(pkgInfo);
    if (jsonPkgInfo.is_null() || jsonPkgInfo.empty()) {
        LOG_SA(ERROR) << "invalid json when parse profile path";
        return false;
    }

    std::string pgoDir = "pgoDir";
    if (!jsonPkgInfo.contains(pgoDir) || jsonPkgInfo[pgoDir].is_null() || !jsonPkgInfo[pgoDir].is_string()) {
        LOG_SA(ERROR) << "invalid pgoDir when parse profile path";
        return false;
    }

    profilePath = jsonPkgInfo[pgoDir].get<std::string>() + "/profile.ap";
    return true;
}

bool StaticAOTArgsParser::ParseProfileUse(HapArgs &hapArgs, std::string &pkgInfo)
{
    std::string profilePath;
    bool parseRet = ParseProfilePath(pkgInfo, profilePath);
    if (!parseRet) {
        LOG_SA(ERROR) << "parse profile path failed in partial mode";
        return false;
    }
    std::string pathArg = PATH + Symbols::EQ + profilePath;
    hapArgs.argVector.emplace_back(Symbols::PREFIX + STATIC_PAOC_USE_PROFILE + Symbols::COLON + pathArg);
    return true;
}

std::optional<std::unique_ptr<AOTArgsParserBase>> AOTArgsParserFactory::GetParser(
    const std::unordered_map<std::string, std::string> &argsMap, bool isEnableStaticCompiler)
{
    std::string arkTsMode;
    if (AOTArgsParserBase::FindArgsIdxToString(argsMap, ArgsIdx::ARKTS_MODE, arkTsMode) != ERR_OK) {
        LOG_SA(INFO) << "aot sa failed to get arkTsMode";
        return std::make_unique<AOTArgsParser>();
    }
    if (arkTsMode == ARKTS_DYNAMIC) {
        LOG_SA(INFO) << "aot sa use default compiler";
        return std::make_unique<AOTArgsParser>();
    }
    if (arkTsMode != ARKTS_STATIC && arkTsMode != ARKTS_HYBRID) {
        LOG_SA(ERROR) << "aot sa get invalid code arkTsMode";
        return std::nullopt;
    }
    // After this, only arkTsMode that is static or hybrid will proceed downwards
    if (!isEnableStaticCompiler) {
        return std::nullopt;
    }
    int32_t isSystemComponent = 0;
    if ((AOTArgsParserBase::FindArgsIdxToInteger(argsMap, ArgsIdx::IS_SYSTEM_COMPONENT, isSystemComponent) != ERR_OK)) {
        LOG_SA(INFO) << "aot sa failed to get isSystemComponent";
    }
    if (isSystemComponent) {
        return std::make_unique<StaticFrameworkAOTArgsParser>();
    }
    return std::make_unique<StaticAOTArgsParser>();
}

bool StaticFrameworkAOTArgsParser::IsFileExists(const std::string &fileName)
{
    std::string realPath;
    if (!panda::ecmascript::RealPath(fileName, realPath)) {
        LOG_SA(ERROR) << "get real path failed:" << fileName;
        return false;
    }
    return panda::ecmascript::FileExist(realPath.c_str());
}

int32_t StaticFrameworkAOTArgsParser::Parse(const std::unordered_map<std::string, std::string> &argsMap,
    HapArgs &hapArgs, [[maybe_unused]] int32_t thermalLevel)
{
    std::string abcPath;
    if ((FindArgsIdxToString(argsMap, ArgsIdx::ABC_PATH, abcPath) != ERR_OK) ||
        (FindArgsIdxToString(argsMap, ArgsIdx::AN_FILE_NAME, hapArgs.fileName) != ERR_OK)) {
        LOG_SA(ERROR) << "aot compiler args parsing error";
        return ERR_AOT_COMPILER_PARAM_FAILED;
    }
    
    if (IsFileExists(hapArgs.fileName)) {
        LOG_SA(INFO) << "framework's an is exist";
        return ERR_AOT_COMPILER_CALL_CANCELLED;
    }

    hapArgs.argVector.clear();
    hapArgs.argVector.emplace_back(STATIC_AOT_EXE);

    hapArgs.signature = OWNERID_SHARED_TAG;

    hapArgs.bundleUid = OID_SYSTEM;
    hapArgs.bundleGid = OID_SYSTEM;
    
    for (auto &defaultArg : staticFrameworkAOTDefaultArgs) {
        hapArgs.argVector.emplace_back(defaultArg);
    }

    std::string fullBootfiles;
    if (!ParseBootPandaFiles(fullBootfiles)) {
        return ERR_AOT_COMPILER_PARAM_FAILED;
    }
    std::string bootfiles = ParseFrameworkBootPandaFiles(fullBootfiles, abcPath);
    if (bootfiles.empty()) {
        LOG_SA(ERROR) << "can not find paoc panda files ";
        return ERR_AOT_COMPILER_PARAM_FAILED;
    }
    hapArgs.argVector.emplace_back(Symbols::PREFIX + STATIC_BOOT_PANDA_FILES + Symbols::EQ + bootfiles);

    for (auto &argPair : argsMap) {
        if (argPair.first == AN_FILE_NAME) {
            hapArgs.argVector.emplace_back(Symbols::PREFIX + STATIC_PAOC_OUTPUT + Symbols::EQ + argPair.second);
        }
    }
    hapArgs.argVector.emplace_back(Symbols::PREFIX + STATIC_PAOC_PANDA_FILES + Symbols::EQ + abcPath);
    return ERR_OK;
}

std::string StaticFrameworkAOTArgsParser::ParseFrameworkBootPandaFiles(const std::string &bootfiles,
    const std::string &paocPandaFiles)
{
    size_t pos = bootfiles.find(paocPandaFiles);
    std::string frameworkBootPandaFiles;
    if (pos != std::string::npos) {
        frameworkBootPandaFiles += bootfiles.substr(0, pos + paocPandaFiles.length());
    }
    return frameworkBootPandaFiles;
}
} // namespace OHOS::ArkCompiler
